package com.googlecode.dgwt.client.dojo;

import java.util.Collection;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.googlecode.dgwt.client.dojo.base.BoxModel;
import com.googlecode.dgwt.client.dojo.core.JsMap;
import com.googlecode.dgwt.client.dojo.core.JsObject;
import com.googlecode.dgwt.client.dojo.impl.DojoImpl;
import com.googlecode.dgwt.client.dojo.impl.HtmlImpl;
import com.googlecode.dgwt.client.dojo.impl.NavigatorImpl;
import com.googlecode.dgwt.client.dojo.impl.ReflectionImpl;
import com.googlecode.dgwt.client.dojo.impl.StringImpl;


/**
 * Supplies the basic Dojo functionalities.
 * 
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class Dojo implements EntryPoint {

    
    public static final HtmlImpl html;
    public static final StringImpl string;
    public static final ReflectionImpl reflect;
    public static final NavigatorImpl navigator;
    
    
    /**
     * Dojo implementation
     */
    private static final DojoImpl impl;

    
    static {
        impl = GWT.create(DojoImpl.class);
        html = impl.html;
        string = impl.string;
        reflect = impl.reflect;
        navigator = impl.navigator;
    }
    


    /*-- Functions --*/
    
    
    
    /**
     * Returns DOM node with matching 'id' attribute or 'null' if not found, 
     * similar to "$" function in another library.
     * 
     * @param id node id
     * @return the node or null if not found
     */
    public static Element byId(String id) {
        return Document.get().getElementById(id);
    }
    
    
    /**
     * Adds the specified classes to the end of the class list on the passed node.
     * Will not re-apply duplicate classes, except in edge
     * cases when adding multiple classes at once.
     * 
     * @param element DOM node
     * @param className CSS class name
     */
    public static void addClass(Element element, String className) {
        impl.addClass(element, className);
    }

    
    /**
     * Returns whether or not the specified classes are a portion of the
     * class list currently applied to the node. 
     *
     * @param element DOM node
     * @param className CSS class name
     */
    public static boolean hasClass(Element element, String className) {
        return impl.hasClass(element, className);
    }
    
    
    /**
     * Adds a class to node if not present, or removes if present.
     *          
     * @param element DOM node
     * @param className CSS class name
     * @return True if class was not present and has been added
     */
    public boolean toggleClass(Element element, String className) {
        boolean b;
        
        if (b = hasClass(element, className)){
            removeClass(element, className);
        } else {
            addClass(element, className);
        }
        
        return !b;
    }
    
    
    /**
     * Destroy a DOM node. Removes a node from its parent, 
     * clobbering it and all of its children.
     * 
     * @param node Node
     */
    public static void destroy(Node node){
        impl.destroy(node);
    }


    /**
     * Destroy an object
     * @param obj Object
     */
    public static void destroy(Object obj) {
        impl.destroy(obj);
    }

    
    /**
     * Destroy a DOM node
     * @param nodeId
     */
    public static void destroy(String nodeId){
        impl.destroy(byId(nodeId));
    }

    
    /**
     * Determine if an object supports a given method.
     * 
     * @param name object property name
     * @return boolean
     */
    public static boolean exists(String name){
        return impl.exists(name, null);
    }

    
    /**
     * Determine if an object supports a given method.
     * 
     * @param name object property name
     * @param scope parent object from where property will be retrived
     * @return boolean
     */
    public static boolean exists(String name, Object scope){
        return impl.exists(name, scope);
    }
    

    /**
     * Gets the current BoxModel.
     * Box functions will assume this model.
     * 
     * @return BoxModel
     */
    public static BoxModel getBoxModel() {
        return impl.boxModel;
    }


    /**
     * Get a property from a dot-separated string, such as "A.B.C"
     * 
     * Useful for longer api chains where you have to test each object in
     * the chain, or when you have an object reference in string format.
     * 
     * @param name object property name
     */
    public static <T> T getObject(String name){
        return impl.getObject(name, false, null);
    }
    
    
    /**
     * Get a property from a dot-separated string, such as "A.B.C".
     * 
     * Useful for longer api chains where you have to test each object in
     * the chain, or when you have an object reference in string format.
     * 
     * @param name object property name
     * @param create should create it if does not exists?
     */
    public static <T> T getObject(String name, boolean create){
        return impl.getObject(name, create, null);
    }
    
    
    /**
     * Get a property from a dot-separated string, such as "A.B.C".
     * 
     * Useful for longer api chains where you have to test each object in
     * the chain, or when you have an object reference in string format.
     * 
     * @param name object property name
     * @param create should create it if does not exists?
     * @param scope parent object from where property will be retrived
     */
    public static <T> T getObject(String name, boolean create, Object scope){
        return impl.getObject(name, create, scope);
    };
    
    
    /**
     * Get a property from a dot-separated string, such as "A.B.C".
     * 
     * Useful for longer api chains where you have to test each object in
     * the chain, or when you have an object reference in string format.
     * 
     * @param name object property name
     * @param scope parent object from where property will be retrived
     */
    public static <T> T getObject(String name, JavaScriptObject scope) {
        return impl.getObject(name, false, scope);
    }

    /**
     * Gets value on the node for style, handling cross-browser concerns .
     * 
     * Getting the style value uses the computed style for the node, so the
     * value will be a calculated value, not just the immediate node.style
     * value.
     * 
     * Also when getting values, use specific style names, like
     * "borderBottomWidth" instead of "border" since compound values like
     * "border" are not necessarily reflected as expected.
     * 
     * If you want to get node dimensions, use Dojo.getMarginBox() or
     * Dojo.getContentBox().
     * 
     * @param node
     *            node to get style for
     * @param name
     *            the style property to set in DOM-accessor format
     *            ("borderWidth", not "border-width") or an object with
     *            key/value pairs suitable for setting each property
     * @return style value
     */
    public static String getStyle(Node node, String name){
        return impl.getStyle(node, name);
    }
     
    
    public static boolean isBodyLTR(){
        return impl.isBodyLTR();
    }
    

    /**
     * (non-Javadoc)
     * @see #query(String, Node)
     * @param query CSS3 selector text 
     * @return NodeList
     */
    public static <T extends Node> NodeList<T> query(String query){
        return impl.query(query, null);
    }

    
    /**
     * Dojo.query() is the swiss army knife of DOM node manipulation in Dojo.
     * Much like Prototype's "$$" (bling-bling) function or JQuery's "$"
     * function, Dojo.query provides robust, high-performance CSS-based node
     * selector support with the option of scoping searches to a particular
     * sub-tree of a document.
     * 
     * @param query CSS3 selector text
     * @param parent target parent node
     * @return NodeList
     */
    public static <T extends Node> NodeList<T> query(String query, Node parent) {
        return impl.query(query, parent);
    }


    /**
     * (non-Javadoc)
     * @see #query(String, Node)
     * @param query CSS3 selector text 
     * @param parentId target parent node ID
     * @return NodeList
     */
    public static <T extends Node> NodeList<T> query(String query, String parentId) {
        return impl.query(query, parentId);
    }


    /**
     * Returns an node list from a predefined query result
     *  
     * @param nodes list of node 
     * @return NodeList
     */
    public static <T extends Node> NodeList<T> query(Node ...nodes){
        return new NodeList<T>(nodes);
    }


    /**
     * Returns an node list from a predefined query result
     *  
     * @param nodes list of node 
     * @return NodeList
     */
    public static <T extends Node> NodeList<T> query(Collection<Node> nodes){
        return new NodeList<T>(nodes);
    }


    /**
     * (non-Javadoc)
     * @see #query(String, Node)
     */
    static void query(String query, Node parent, NodeList<? extends Node> nodeList) {
        impl.query(query, parent, nodeList);
    }

    
    /**
     * Removes the specified classes from node. No
     * {@link Dojo#hasClass(Element, String)} check is required.
     * 
     * @param node
     *            DOM node
     * @param className
     *            CSS class name
     */
    public static void removeClass(Element node, String className){
        impl.removeClass(node, className);
    };

    
    /**
     * Sets the current BoxModel. Box functions will assume this model.
     * 
     * @param boxModel
     *            the new box model
     */
    public static void setBoxModel(BoxModel boxModel) {
        if (boxModel == null){
            boxModel = BoxModel.CONTENT_BOX;
        }
        impl.boxModel = boxModel;
    }

    
    /**
     * Set a property from a dot-separated string, such as "A.B.C"
     * 
     * Useful for longer api chains where you have to test each object in the
     * chain, or when you have an object reference in string format.
     * 
     * Objects are created as needed along `name`. Returns the passed value if
     * setting is successful or NULL if not.
     * 
     * @param name
     *            a dot-separated property name
     * @param value
     *            value to define
     * @return the new value if setting is successful or NULL if not.
     */
    public static <T> T setObject(String name, T value){
        return impl.setObject(name, value, null);
    }
    
    
    /**
     * Set a property from a dot-separated string, such as "A.B.C"
     * 
     * Useful for longer api chains where you have to test each object in the
     * chain, or when you have an object reference in string format.
     * 
     * Objects are created as needed along `name`. Returns the passed value if
     * setting is successful or NULL if not.
     * 
     * @param name
     *            a dot-separated property name
     * @param value
     *            value to define
     * @param scope
     *            the object to set a property. Defaults to Dojo.getGlobal()
     * @return the new value if setting is successful or NULL if not.
     */
    public static <T> T setObject(String name, T value, JavaScriptObject scope){
        return impl.setObject(name, value, scope);
    }

    
    /**
     * Accesses styles on a node
     * 
     * @param node
     *            node to set style for
     * 
     * @param name
     *            the style property to set in DOM-accessor format
     *            ("borderWidth", not "border-width") or an object with
     *            key/value pairs suitable for setting each property
     * 
     * @param value
     *            sets value on the node for style, handling cross-browser
     *            concerns
     */
    public static void setStyle(Node node, String name, String value){
        impl.setStyle(node, name, value);
    }
    
    
    /**
     * Instantiates an HTML fragment returning the corresponding DOM.
     * 
     * @param html
     *            the HTML fragment
     * @return Node
     */
    public static Node toDom(String html){
        return toDom(html, null, null);
    }

    
    /**
     * Instantiates an HTML fragment returning the corresponding DOM.
     * 
     * @param html
     *            the HTML fragment
     * @param doc
     *            owner document for the created nodes
     * @return Node
     */
    public static Node toDom(String html, Document doc){
        return toDom(html, doc, null);
    }
    
    /**
     * Instantiates an HTML fragment returning the corresponding DOM.
     * 
     * @param html
     *            the HTML fragment
     * @param doc
     *            owner document for the created nodes
     * @param multipleNodesContainerTagName
     *            element tag name to wrap result if there is more than one
     * @return Node
     */
    public static Node toDom(String html, Document doc, String multipleNodesContainerTagName) {
        return impl.toDom(html, doc, multipleNodesContainerTagName);
    }


    /**
     * Initialization
     */
    @Override
    public void onModuleLoad() {		
        impl.onModuleLoad();		
    }


    /**
     * Gets the alias for the global scope (e.g. the window object in a
     * browser).
     * 
     * @return window
     */
    public static native JsObject getGlobal() /*-{
        return $wnd;
    }-*/;


    /**
     * Gets a "computed style" object which can be used to gather information
     * about the current state of the rendered node.
     * 
     * Note that this may behave differently on different browsers. Values may
     * have different formats and value encodings across browsers.
     * 
     * Note also that this method is expensive. Wherever possible, reuse the
     * returned object. Use the {@link Dojo#getStyle(Node, String)} method for more consistent
     * (pixelized) return values.
     * 
     * @param node
     *            DOM node
     * @return native computed style object
     */
    public static <V> JsMap<V> getComputedStyle(Node node) {
        return impl.getComputedStyle(node);
    }    

    
    /* TODO public static <T extends JavaScriptObject> T clone(T obj) { return
     * impl.clone(obj); }
     */

    // FIXME funciona em ambos os modos: Hosted mode e Web mode.
    // porém GWT não deixa compilar quando é pra gerar o código final.
    // vou aguardar para ver em versões futuras se dá para usar.
    public static <T extends Cloneable> T clone(T obj) {
        return impl.clone(obj);
    }

    /**
     * Verify if two objects are equals with <code>null</code> check.
     * 
     * @param a
     *            an object
     * @param b
     *            an object
     * @return True if they are equals
     */
    public static boolean isEquals(Object a, Object b) {
        if (a == b) {
            return true;
        } else if (a == null) {
            return false;
        } else {
            return a.equals(b);
        }
    }

    public static native void ensureVerticalVisibility(Element scroll, Element element, boolean centralize) /*-{
        if (!element || !scroll) return; 
    
        var item = element;
        var realOffset = 0;
        while (item && (item != scroll)) {
            realOffset += item.offsetTop;
            item = item.offsetParent;
        }
        
        if (centralize){
            scroll.scrollTop = realOffset - scroll.offsetHeight / 2;
            return;
        }
        
        var scrollTop = scroll.scrollTop;
        
        if (scrollTop > realOffset){
            scroll.scrollTop = realOffset;
            return;
        }

        var scrollHeight = scroll.offsetHeight;
        var itemHeight = element.offsetHeight;

        if (scrollTop + scrollHeight < realOffset + itemHeight){
            scroll.scrollTop = realOffset + itemHeight - scrollHeight;
        }
    }-*/;

}
